/* AutocompleterWidget.js
 *
 * Copyright 2006, Tim Dwelle.
 *
 * Licensed under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in
 * compliance with the License.  You may obtain a copy of
 * the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in
 * writing, software distributed under the License is
 * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
 * CONDITIONS OF ANY KIND, either express or implied.  See
 * the License for the specific language governing
 * permissions and limitations under the License.
 *
 */

$package("dowry.widget");

/**
 * AutocompleterWidget provides a text input field with the
 * ability to offer server-side completion of text entry.
 *
 * <p>
 * This class uses <a href="http://script.aculo.us/">
 * Scriptaculous</a> to provide the autocomplete
 * functionality.  Since this code is not bundled with
 * Dowry, please be sure to include the associated scripts,
 * stylesheets, etc. for this external library.
 * </p>
 *
 */
$class("AutocompleterWidget").$extends("TextInputWidget").$with("Transformable").$as(
{
    /**
     * Constant for the prefix to place before the ID of the
     * div containing the autocompleter choices, to make the
     * identifier unique..
     *
     */
    AUTOCOMPLETE_PREFIX: "_dowry_widget_AutocompleterWidget_choices_",

    /**
     * Constant for the CSS class name to apply to the
     * autocomplete options.
     *
     */
    CSS_AUTOCOMPLETE : "autocomplete",

    /**
     * Reference to the DWR remoting function to calculate
     * the autocomplete choices.  This may be a string
     * containing the name of the function (such as
     * <code>"MyService.doSomething"</code>) or may be a
     * reference to the function itself.
     *
     */
    remote : null,

    /**
     * Property representing the Scriptaculous autocompleter
     * component itself.
     *
     */
    _ac : null,

    /**
     * Property representing the string ID of the element
     * representing the update block where the autocomplete
     * options are populated.
     *
     */
    _updateId : null,

    /**
     * Constructs the AutcompleterWidget and builds the
     * update block where the autcomplete options are
     * populated.
     *
     * @param ele    the element (or ID of the element)
     *               to bind to this datatype
     *
     */
    AutocompleterWidget : function(ele)
    {
        this.$super(arguments);

        var update = this.createUpdate();

        if (ele &&
            update &&
            update.id &&
            window.Autocompleter)
        {
            var span = this._createEnclosingSpan(ele);
            span.appendChild(update);
            this._updateId = update.id;
        }
    },

    /**
     * Creates a <code>&lt;li&gt;</code> element.
     *
     * @param val        the value of the item
     *
     * @param itemIndex  the index of the item
     *
     * @return           the li element
     *
     */
    createListItem : function(val, itemIndex)
    {
        return document.createElement("li");
    },

    /**
     * Creates the list that the list items get appended to,
     * in this case a simple <code>&lt;ul&gt;</code>
     * element.
     *
     * @return  the ul element
     *
     */
    createList : function()
    {
        return document.createElement("ul");
    },

    /**
     * Creates a <code>&lt;div&gt;</code> element where
     * autocompletion options will be populated.  This div
     * will automatically be given a class declaration of
     * 'autocomplete' so it can be styled appropriately.
     *
     * @return the div element
     *
     */
    createUpdate : function()
    {
        var div = null;

        var ele = this.getElement();
        if (ele)
        {
            div = document.createElement("div");
            div.setAttribute("id",  this.AUTOCOMPLETE_PREFIX + ele.id);
            div.setAttribute("class", this.CSS_AUTOCOMPLETE);
            Dowry.css.addClassName(div, this.CSS_AUTOCOMPLETE);
            div.appendChild(document.createTextNode(" "));
        }

        return div;
    },

    /**
     * Gets an object containing the options for the
     * Scriptaculous autocompleter to be used with the
     * autocompleter's <code>baseIntialize()</code>
     * function.  By default, this object is empty.
     *
     * @return an object representing the options for the
     *         Scriptaculous autocompleter
     *
     */
    getAutocompleterOptions : function()
    {
        return new Object();
    },

    /**
     * When the AutocompleterWidget is started, associate
     * events to validate the input, as well as the events
     * to initialize the autcompletion behavior.
     *
     */
    start : function()
    {
        this.$super(arguments);

        var ele = this.getElement();
        var update = $(this._updateId);

        if (ele && update)
        {
            var options = this.getAutocompleterOptions();
            if (!options)
            {
                options = new Object();
            }

            var ac = new Autocompleter.Base();
            ac.baseInitialize(ele, update, options);

            // TODO: can this just be
            // ac.getUpdatedChoices = this._getUpdatedChoices;
            // ?
            ac.widget = this;
            ac.getUpdatedChoices = function()
            {
                this.widget._getUpdatedChoices();
            };

            this._ac = ac;
        }
    },

    /**
     * Gets the updated choices for the autocompleter using
     * the specified remote
     *
     */
    _getUpdatedChoices : function()
    {
        // TODO: the getToken() stuff is nice...
        // some way to interface this into the code
        // without breaking the datatype stuff?
        var val = null;
        var ele = this.getElement();
        if (ele != null && ele.value != null)
        {
            val = ele.value;
        }

        // if remote is a string, see if we can
        // resolve it to a function
        var func = this.remote;
        if (typeof func == "string")
        {
            var o = window;
            var nodes = func.split(".");
            var node = null;
            for (var i=0; i < nodes.length; i++)
            {
                node = nodes[i];
                o = o[node];
                if (!o)
                {
                    break;
                }
            }

            func = o;
        }

        if (typeof func == "function")
        {
            var callback = this._remoteCallback.callback($VALUE);
            func.call(null, val, callback);
        }
    },

    /**
     * On remote callback, process the data returned into
     * a list and then have the autocompleter display the
     * list of choices.
     *
     */
    _remoteCallback : function(vals)
    {
        var temp = document.createElement("span");

        var ul = this.createList();
        if (ul != null)
        {
            temp.appendChild(ul);

            vals = this.transform(vals);
            var ele = this.getElement();

            if (ele != null &&
                vals != null)
            {
                for (var i=0; i < vals.length; i++)
                {
                    var val = vals[i];

                    // convert the raw value
                    var dt = this.getDatatype();
                    if (dt != null)
                    {
                        val = dt.toFormattedString(val);
                    }

                    // add a new list item
                    var li = this.createListItem(val, i);
                    if (li != null)
                    {
                        this._setValueImpl(li, val);
                        ul.appendChild(li);
                    }
                }
            }
        }

        this._ac.updateChoices(temp.innerHTML);
    }
});